package com.dz.finance.biz.service.impl;

import com.dz.finance.biz.config.ObjectConvertor;
import com.dz.finance.biz.constant.MsgConstant;
import com.dz.finance.biz.constant.RedisKeyConstant;
import com.dz.finance.biz.dto.PhoneRegisterDTO;
import com.dz.finance.biz.entity.MemberBindPhone;
import com.dz.finance.biz.entity.MemberBindWxOpenId;
import com.dz.finance.biz.enums.SmsCodeTypeEnum;
import com.dz.finance.biz.service.*;
import com.dz.finance.biz.vo.GenerateMpRegCodeVo;
import com.dz.finance.common.entity.Token;
import com.dz.finance.common.entity.User;
import com.dz.finance.common.service.UserTokenService;
import com.dz.finance.wechat.entity.AccessToken;
import com.dz.finance.wechat.entity.MpQrCodeCreateReq;
import com.dz.finance.wechat.entity.MpQrCodeCreateResp;
import com.dz.finance.common.exception.BizException;
import com.dz.finance.common.exception.ParameterException;
import com.dz.finance.wechat.config.WeChatConfig;
import com.dz.finance.wechat.entity.MpSubscribeEventReq;
import com.dz.finance.wechat.service.WeChatService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author: DengHaoQi
 * @date: 2024/5/31 17:37
 * @description:
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class MemberRegisterServiceImpl implements MemberRegisterService {

    private final MemberLoginService memberLoginService;
    private final MemberBindPhoneService memberBindPhoneService;
    private final MemberService memberService;
    private final TenantService tenantService;
    private final RedissonClient redissonClient;
    private final TransactionTemplate transactionTemplate;  // 事务
    private final WeChatConfig weChatConfig;
    private final WeChatService weChatService;
    private final ObjectConvertor objectConvertor;
    private final MemberBindWxOpenIdService wxOpenIdService;
    private final UserTokenService<User> tokenService;
    private final RedisTemplate<String, Object> redisTemplate;

    @Override
    public Long regPhone(PhoneRegisterDTO phoneRegisterDTO) {

        // 校验两次密码是否一致
        if (!phoneRegisterDTO.getPassword().equals(phoneRegisterDTO.getConfirmPassword())) {
            throw new ParameterException(MsgConstant.TWO_PASSWORDS_DO_NOT_MATCH);
        }

        // 校验短信验证码
        boolean checkSmsCode = memberLoginService.checkSmsCode(phoneRegisterDTO.getPhone(),
                phoneRegisterDTO.getSmsCode(),
                SmsCodeTypeEnum.REG.getCode());
        if (!checkSmsCode) {
            throw new ParameterException(MsgConstant.PASSCODE_ERROR);
        }

        // 校验当前手机号是否已经注册
        MemberBindPhone mbp = new MemberBindPhone();
        mbp.setPhone(phoneRegisterDTO.getPhone()); // 封装查询条件
        RLock rLock = redissonClient.getLock("PHONE_REG" + phoneRegisterDTO.getPhone());
        try {
            rLock.lock(); // 避免并发注册同一个手机号的问题
            MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberBindPhone(mbp);
            if (Objects.nonNull(memberBindPhone)) {
                log.warn("手机号: {} 已被注册", phoneRegisterDTO.getPhone());
                throw new BizException(MsgConstant.PHONE_REGISTERED);
            }

            // 没被注册，将注册信息写入DB(事务)
            Long execute = transactionTemplate.execute(status -> {
                Long tenantId = tenantService.addTenant();
                Long memberId = memberService.reg(tenantId);
                memberBindPhoneService.reg(memberId, phoneRegisterDTO.getPhone(), phoneRegisterDTO.getPassword());
                return memberId;
            });
            if (Objects.isNull(execute)) {
                throw new BizException(MsgConstant.REGISTER_FAIL);
            }
            return execute;
        } catch (Exception ex) {
            throw new BizException(ex.getMessage());
        } finally {
            rLock.unlock();
        }

    }


    /*
     * 生成微信公众号二维码
     * 参考文档: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
     * */
    @Override
    public GenerateMpRegCodeVo generateMpRegCode(String clientId) {
        GenerateMpRegCodeVo vo = (GenerateMpRegCodeVo) redisTemplate.opsForValue().get(RedisKeyConstant.QR_CODE + clientId);
        if (Objects.nonNull(vo)) {
            return vo;
        }

        // 获取接口调用凭证
       AccessToken accessToken = weChatService.getWeChatAccessToken(weChatConfig.getMp().getAppId(), weChatConfig.getMp().getAppSecret());
        // AccessToken accessToken = weChatService.getAccessTokenInCache(weChatConfig.getMp().getAppId());
        // 封装微信接口参数
        MpQrCodeCreateReq mReq = new MpQrCodeCreateReq();
        mReq.setExpireSeconds(weChatConfig.getMp().getCodeExpire());
        mReq.setActionName("QR_STR_SCENE");
        mReq.setActionInfo(mReq.new ActionInfo());
        mReq.getActionInfo().setScene(mReq.new scene());
        mReq.getActionInfo().getScene().setSceneStr("ScanReg_" + weChatConfig.getMp().getAppId() + "_" + clientId);

        // 调用微信接口，获取创建二维码ticket并生成二维码地址
        MpQrCodeCreateResp mResp = weChatService.createQrCode(accessToken.getAccessToken(), mReq);
        GenerateMpRegCodeVo generateMpRegCodeVo = objectConvertor.toGenerateMpRegCodeVo(mResp);
        redisTemplate.opsForValue().set(RedisKeyConstant.QR_CODE + clientId, generateMpRegCodeVo, generateMpRegCodeVo.getExpireSeconds(), TimeUnit.SECONDS);
        return generateMpRegCodeVo;
    }


    /*
     * 监听处理关注/取消关注事件
     * */
    @Override
    @EventListener
    public void handleMpSubscribeEvent(MpSubscribeEventReq event) {

        log.info("接收到的消息:{}", event);
        if ("subscribe".equals(event.getEvent()) && Strings.isNotBlank(event.getEventKey())) {
            // 订阅;注册
            String[] s = event.getEventKey().split("_");
            if ("qrscene".equals(s[0]) && "ScanReg".equals(s[1])) {
                log.info("AppId：{}，ClientId：{}", s[2], s[3]);
                regMpOpenId(s[2], event.getFromUserName(), s[3]);
                return;
            }
        }

        /*if ("unsubscribe".equals(event.getEvent())) {
            // 取消关注

            // 清除用户信息
        }*/

        if ("SCAN".equals(event.getEvent()) && Strings.isNotBlank(event.getEventKey())) {
            // 用户已关注时的事件推送
            String[] s = event.getEventKey().split("_");
            if ("ScanReg".equals(s[0])) {
                regMpOpenId(s[1], event.getFromUserName(), s[2]);
                return;
            }
        }
    }


    /*
     * 为注册的用户构造token
     * */
    @Override
    public Token regMpOpenId(String appId, String openId, String clientId) {
        Long memberId = scanReg(appId, openId);
        User user = new User();
        user.setId(memberId);
        tokenService.buildToken(user);
        redisTemplate.opsForValue().set(RedisKeyConstant.CLIENT_TOKEN_KEY + clientId, user.getToken(), 10, TimeUnit.MINUTES);
        return user.getToken();
    }



    /*
     * 注册写表
     * */
    private Long scanReg(String appId, String openId) {
        MemberBindWxOpenId condition = new MemberBindWxOpenId();
        condition.setOpenId(openId);
        MemberBindWxOpenId member = wxOpenIdService.getWxBindOpenId(condition);

        // 如果已经注册
        if (Objects.nonNull(member)) {
            return member.getMemberId();
        }

        // 没有注册
        Long execute = transactionTemplate.execute(t -> {
            Long tenantId = tenantService.addTenant();
            Long memberId = memberService.reg(tenantId);
            wxOpenIdService.reg(appId, openId, memberId);
            return memberId;
        });
        if (Objects.isNull(execute)) {
            throw new BizException(MsgConstant.REGISTER_FAIL);
        }
        return execute;
    }


}
